#ifndef LW_DYNAMIC_ARRAY2_H
#define LW_DYNAMIC_ARRAY2_H

#include <string>

/**
 * 2D Array.
 * Size specified at runtime.
 */
template<typename T>
class DynamicArray2 {
protected:
	T* a;
	const int size1;
	const int size2;
	static const std::string indexOutOfBoundsMessage;
	static const std::string sizeMismatchMessage;

protected:
	inline int GetFlatIndex(int index1, int index2) {
		return index1 + index2*size1;
	}

public:
	/**
	 * Creates an array with the specified size.
	 */
	DynamicArray2(int size1, int size2) : size1(size1), size2(size2) {
	    this->a = new T[size1*size2];
	}

	/**
	 * Creates this array as a copy of the specified array.
	 */
	DynamicArray2(const DynamicArray2<T>& v) : size1(size1), size2(size2) {
		if(this != &v) {
			this->a = new T[size1*size2];

			for(int j=0; j<size2; ++j) {
				for(int i=0; i<size1; ++i) {
					const int index = GetFlatIndex(i, j);
					this->a[index] = v.a[index];
				}
			}
		}
	}

	/**
	 * Deletes this array and all internal created data.
	 */
	virtual ~DynamicArray2() {
		delete[] a;
	}

	/**
	 * Gets the pointer to the 1D Array containing all elements.
	 */
	inline T* GetData() {
		return a;
	} 

	/**
	 * Returns the first dimension size of this array.
	 */
	inline int Size1() {
		return size1;
	}

	/**
	 * Returns the second dimention size of this array.
	 */
	inline int Size2() {
		return size2;
	}

	/**
	 * Returns the value referenced by the specified indeces.
	 */
    inline const T& operator()(int index1, int index2) const {
        if (index1 >= 0 && index1 < size1 && index2 >= 0 && index2 < size2) {
            return a[GetFlatIndex(index1, index2)];
        }

        throw std::out_of_range(indexOutOfBoundsMessage);
    }

	/**
	 * Returns or sets the value referenced by the specified indeces.
	 */
	inline T& operator()(int index1, int index2) {
		if (index1 >= 0 && index1 < size1 && index2 >= 0 && index2 < size2) {
			return a[GetFlatIndex(index1, index2)];
		}

		throw std::out_of_range(indexOutOfBoundsMessage);
	}

	/**
	 * Returns the value referenced by the specified flat index.
	 */
    inline const T& operator[](int flatIndex) const {
        if (flatIndex < size1*size2 && flatIndex >= 0) {
            return a[flatIndex];
        }

        throw std::out_of_range(indexOutOfBoundsMessage);
    }

	/**
	 * Returns the value referenced by the specified flat index.
	 */
    inline T& operator[](int flatIndex) {
        if (flatIndex < size1*size2 && flatIndex >= 0) {
            return a[flatIndex];
        }

        throw std::out_of_range(indexOutOfBoundsMessage);
    }

	/**
	 * Compares this array with the specified array, value by value.
	 * Returns true if equal.
	 */
	bool operator==(const DynamicArray2<T>& v) const {
		if(this->size1 == v.size1 && this->size2 == v.size2) {
			for(int j=0; j<size2; ++j) {
				for(int i=0; i<size1; ++i) {
					const int index = GetFlatIndex(i, j);
					if(this->a[index] != v.a[index]) {
						return false;
					}
				}
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Compares this array with the specified array, value by value.
	 * Returns true if not equal.
	 */
	bool operator!=(const DynamicArray2<T>& v) const {
		if(this->size1 == v.size1 && this->size2 == v.size2) {
			for(int j=0; j<size2; ++j) {
				for(int i=0; i<size1; ++i) {
					const int index = GetFlatIndex(i, j);
					if(this->a[index] != v.a[index]) {
						return true;
					}
				}
			}
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Assignes the specified array to this array, value by value.
	 * Throws exception if the sizes of the arrays are not equal.
	 */
	DynamicArray2<T>& operator=(const DynamicArray2<T>& v) {
		if(this != &v) {
			if(this->size1 != v.size1 || this->size2 != v.size2) {
				throw std::range_error(sizeMismatchMessage);
			}

			for(int j=0; j<size2; ++j) {
				for(int i=0; i<size1; ++i) {
					const int index = GetFlatIndex(i, j);
					this->a[index] = v.a[index];
				}
			}
		}

		return *this;
	}
};

template<typename T>
const std::string DynamicArray2<T>::indexOutOfBoundsMessage = "Array index out of range";

template<typename T>
const std::string DynamicArray2<T>::sizeMismatchMessage = "Array size mismatch";

#endif //LW_DYNAMIC_ARRAY2_H
